import datetime
import json
import logging
import re
import webapp2

from google.appengine.api import taskqueue
from google.appengine.api import urlfetch
from google.appengine.ext import db

from lib import models
from lib import utils
import config


class OnChannelConnect(webapp2.RequestHandler):
  # Does not retry on failure
  def post(self):
    client_id = self.request.get('from')
    channel = models.Channel.all().filter('client_id =', client_id).fetch(1)[0]
    channel.active = True
    channel.put()


class OnChannelDisconnect(webapp2.RequestHandler):
  # Does not retry on failure
  def post(self):
    client_id = self.request.get('from')
    channel = models.Channel.all().filter('client_id =', client_id).fetch(1)[0]
    channel.active = False
    channel.put()


class PollFeed(webapp2.RequestHandler):
  _YOUTUBE_ID_RE = re.compile('v=(?P<id>.*?)[&$]')

  @db.transactional
  def _Enqueue(self):
    key = self.request.get('key')
    feed = models.Feed.get(key)
    if feed.subscription_set.ancestor(feed).fetch(1):
      # We still have at least 1 subscriber, soldier on
      taskqueue.add(queue_name='feeds',
                    url='/services/pollFeed',
                    params={'key': key},
                    countdown=config.FEED_POLL_SECONDS,
                    transactional=True)
    else:
      # No subscribers, stop polling
      feed.active = False
      feed.put()

  def _FetchContent(self, url, feed):
    response = urlfetch.fetch(url,
                              method=urlfetch.HEAD,
                              validate_certificate=True)
    if ('etag' in response.headers and
        feed.etag and
        response.headers['etag'] == feed.etag):
      return None

    response = urlfetch.fetch(url,
                              method=urlfetch.GET,
                              validate_certificate=True)
    feed.etag = response.headers.get('etag', None)
    feed.put()
    return json.loads(response.content)

  def _HandleYouTube(self, url, feed):
    source_media = {}
    content = self._FetchContent(url, feed)
    if not content:
      return

    entries = content['feed'].get('entry', [])
    for entry in entries:
      id_match = self._YOUTUBE_ID_RE.search(entry['media$group']['media$player']['url'])
      source_media[id_match.group('id')] = models.Media(
        parent=feed,
        feed=feed,
        source_id=id_match.group('id'),
        title=entry['media$group']['media$title']['$t'],
        description=entry['media$group']['media$description']['$t'],
        duration_seconds=int(entry['media$group']['yt$duration']['seconds']),
      )
    # TODO(flamingcow): Fetch more pages

    to_delete = []
    for entry in feed.media_set:
      if entry.source_id in source_media:
        # TODO(flamingcow): Check that the values are unchanged
        del source_media[entry.source_id]
      else:
        to_delete.append(entry)

    if not to_delete and not source_media:
      return

    for sub in feed.subscription_set:
      for media in to_delete:
        sub.channel.SendMessage('media-delete',
                                feed.path,
                                media.to_dict())
      for media in source_media.itervalues():
        sub.channel.SendMessage('media-add',
                                feed.path,
                                media.to_dict())

    if to_delete:
      db.delete(to_delete)

    if source_media:
      db.put(source_media.values())

  @utils.chaos_monkey
  def post(self):
    key = self.request.get('key')
    feed = models.Feed.get(key)
    _, source, content_type, content_id = feed.path.split('/')
    if source == 'youtube':
      if content_type == 'channel':
        url = 'https://gdata.youtube.com/feeds/api/users/%s/uploads?v=2&alt=json' % content_id
        self._HandleYouTube(url, feed)

    self._Enqueue()


class DeleteIdleChannels(webapp2.RequestHandler):
  def _Enqueue(self):
    taskqueue.add(queue_name='cleanup',
                  url='/services/deleteIdleChannels',
                  countdown=config.CHANNEL_CLEANUP_SECONDS)

  def _EntitiesToDelete(self):
    cutoff = datetime.datetime.now() - datetime.timedelta(0, config.CHANNEL_IDLE_SECONDS)
    channels = (models.Channel.all()
                .filter('active =', False)
                .filter('last_seen <', cutoff))
    for channel in channels:
      for subscription in channel.subscription_set:
        yield subscription
      channel.SendMessage('reconnect')
      yield channel

  @utils.chaos_monkey
  def post(self):
    db.delete(self._EntitiesToDelete())
    self._Enqueue()


app = webapp2.WSGIApplication([
  ('/_ah/channel/connected/', OnChannelConnect),
  ('/_ah/channel/disconnected/', OnChannelDisconnect),
  ('/services/deleteIdleChannels', DeleteIdleChannels),
  ('/services/pollFeed', PollFeed),
])
